#!/usr/bin/perl -w

package Scheduler;
use Switch;
use Tupla;
use diagnostics;
use strict;
use warnings;

sub new
{
    my $class = shift;
    my $self = {
        _ready		=> [],
        _executing	=> undef,
        _executing2	=> undef,
        _IO1		=> [],
        _IO2		=> [],
        _IO3		=> [],
        _ticks		=> 0,
        _output		=> undef,
        _auxList	=> [],
        _auxProc    => undef,
        _quantumUltUsed  => 0,
        _quantumUltUsed2 => 0,
        _antName => '',
        _antName2 => '',
        _auxUltListIO1 => [],
        _auxUltListIO2 => [],
        _auxUltListIO3 => [],
        _auxIO		=> [],
        _first		=> 1,
        
    };
    
    $self->{_output} = shift;
    $self->{_kernels} = shift;
    $self->{_ultAlgorithmin} = shift;
    $self->{_ThreadQuantum}=shift;
        
    bless $self, $class;
    return $self;
}

sub tick{
	my( $self ) = @_;
	$self->sendReadys();
	$self->getUltsNews();
	$self->doTick();
	$self->{_output}->print($self);
	$self->_doTickIO1();
	$self->_doTickIO2();
	$self->_doTickIO3();
	$self->_rearrangeIO();
	$self->_doProcess(1);
	if($self->{_kernels}>1){
			$self->_doProcess(2);
		}
	$self->_doAfterProcess();
	$self->_moveToReady();
	$self->{_ticks} += 1;
}

sub getUltsNews{
	my( $self ) = @_;
	my $proc;
	
	
	foreach $proc(@{$self->{_ready}}){
		$proc->getNewsUlts($self->{_ticks});
	} 
	
	foreach $proc(@{$self->{_IO1}}){
		$proc->getNewsUlts($self->{_ticks});
	} 
	
	foreach $proc(@{$self->{_IO2}}){
		$proc->getNewsUlts($self->{_ticks});
	} 
	
	foreach $proc(@{$self->{_IO3}}){
		$proc->getNewsUlts($self->{_ticks});
	} 
	
	if($self->{_executing}){
		$self->{_executing}->getNewsUlts($self->{_ticks});
		
		if($self->{_ultAlgorithmin} eq "SRT"){
			$self->{_executing}->orderUltByMinor();	
		}
	} 
	
	if($self->{_executing2}){
		$self->{_executing2}->getNewsUlts($self->{_ticks});
		
		if($self->{_ultAlgorithmin} eq "SRT"){
			$self->{_executing2}->orderUltByMinor();	
		}
	} 
}

sub sendReadys{
	my( $self ) = @_;
	if($self->{_first}){
		my $process;
		foreach $process (@{$self->{_ready}}){
			$self->{_output}->putIntoReady($process);
		}
		$self->{_first} = 0;
	}
}
sub doTick{
	my( $self ) = @_;
}

sub _doAfterProcess{
	my( $self ) = @_;
}

sub _doAfterBloqued{
	my( $self ) = @_;
}
	
sub _doProcess{
	my ($self,$kernel) = @_;
	my $currentProcess;
	my $hasUlts=0; #Variable auxiliar sirve para después de Shiftear el último ult.
	my $hasIO=0;
	
	
	if($kernel == 1){
		$currentProcess = $self->{_executing};
	}else{
		$currentProcess = $self->{_executing2};
	}	 
		
	if($currentProcess){
		
		if($currentProcess->hasUlts()){
			$hasUlts=1;
			$self->{_auxProc} = $currentProcess;
			$currentProcess = shift @{$self->{_auxProc}->{_ults}}; #Saco el primer ult de la lista aux del proceso. (Siempre va a estar ordenada segun algoritmo).
		}
		
		my @bursts = @{$currentProcess->getBursts()};
		my $burstsLength = @bursts;

		if($burstsLength){
			if($bursts[0]->getName() eq "cpu"){
				my $cpu = $bursts[0]->getValue();
				$bursts[0]->setValue($cpu-1);
				if($bursts[0]->getValue()>0){
					
					
					if($hasUlts){
						$self->_ProcessUltByExcecuting($currentProcess, $kernel); #Me va a agregar al proceso _axuProc con los ults ordenados por Algoritmos.
						$currentProcess = $self->{_auxProc};
					}
					
					if($currentProcess){					
						if($kernel == 1){
							$self->{_executing} = $currentProcess;
						}else{
							$self->{_executing2} = $currentProcess;
						}
					}else{ #Es para RR de ult.
						if($kernel == 1){
							$self->{_executing} = undef;
						}else{
							$self->{_executing2} = undef;
						}
					}
					
					
				}else{
					shift @bursts;
					$currentProcess->setBursts(\@bursts);
					$burstsLength = @{$currentProcess->getBursts()};
					
					if($burstsLength){
							
							if($hasUlts){
								unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;#va a ser lo mismo para c/Algoritmo de Biblioteca.
								$currentProcess = $self->{_auxProc}; 						
							}
							
							$hasIO = 1;
							
							switch($bursts[0]->getName()){
								case "io1" { 
									push $self->{_IO1}, $currentProcess;
								}
								case "io2" { 
									push $self->{_IO2}, $currentProcess;
								}
								case "io3" { 
									push $self->{_IO3}, $currentProcess;
								}
								case "cpu" {
									unshift @{$self->{_ready}}, $currentProcess;
								}
							
															
							}
							
							if($kernel == 1){
									$self->{_quantumUltUsed} = 0;
							}else{
									$self->{_quantumUltUsed2} = 0;
							}
							
							if($currentProcess->hasUlts()){
								$self->_doAfterBloqued($self, $kernel);
							}
					}
																				
					if($kernel == 1){
								
						if($hasUlts && !$hasIO){
							if($self->{_auxProc}->hasUlts()){
								
								if($self->{_ultAlgorithmin} eq "SPN" || $self->{_ultAlgorithmin} eq "SRT"){
									$self->_OrderUltByMinor();
								}elsif($self->{_ultAlgorithmin} eq "HRRN"){
											$self->{_auxProc}->orderUltByPriorityHRRN($self->{_ticks});
										}
								
								$self->{_executing} = $self->{_auxProc};
								
															
							}else{
								
								if($self->{_auxProc}->hasUltsNew()){
									push @{$self->{_auxList}}, $self->{_auxProc};
								}
								
								$self->{_executing} = undef;
								$self->{_quantumUltUsed}=0;
							}
							
						}else{
							$self->{_executing} = undef;
						}
					}else{
							if($hasUlts && !$hasIO){
								if($self->{_auxProc}->hasUlts()){
									
										if($self->{_ultAlgorithmin} eq "SPN" || $self->{_ultAlgorithmin} eq "SRT"){
											$self->_OrderUltByMinor();
										}elsif($self->{_ultAlgorithmin} eq "HRRN"){
											$self->{_auxProc}->orderUltByPriorityHRRN($self->{_ticks});
										}
									
										$self->{_executing2} = $self->{_auxProc};
								
								}else{
									
									if($self->{_auxProc}->hasUltsNew()){
										push @{$self->{_auxList}}, $self->{_auxProc};
									}
									
									$self->{_executing2} = undef;
									$self->{_quantumUltUsed2}=0;
								}
								
							}else{
								$self->{_executing2} = undef;
							}
					}	
					
					if(!$currentProcess->hasUlts()){
						$self->_doAfterBloqued($self, $kernel); #Vuelvo el Quantum a cero cuando el proceso se Bloquea.
					}
				}
			}
		}else{
			if($kernel == 1){
				#y pregunta si el proceso tiene un indice de proceso padre, entonces lo busco en la lista auxiliar y si le quedan ults por procesar
				#no lo saco de executing, pero le actualizo los ults
				
				if($currentProcess->hasUltsNew()){
					push @{$self->{_auxList}}, $currentProcess;
				}
					$self->{_executing} = undef;
			
				
				
			}else{
				if($currentProcess->hasUltsNew()){
					push @{$self->{_auxList}}, $currentProcess;
				}
					$self->{_executing2} = undef;
				
			}
		}
	}
}

sub _ProcessUltByExcecuting{
	my( $self, $currentProcess, $kernel ) = @_;
	
	switch($self->{_ultAlgorithmin}){
		
		case "FIFO" {
			unshift @{$self->{_auxProc}->{_ults}}, $currentProcess; #SI es FIFO y está ejecutando va a ser el primer ult en volver a ejecutar ráfagas.
		}
		case "RR" {
			
			if($kernel==1){
				
					$self->{_quantumUltUsed} +=1;
					if($currentProcess->{_quantumLeft} > 0){
							$self->{_quantumUltUsed} += $currentProcess->{_quantumLeft} ? ($self->{_ThreadQuantum} - $currentProcess->{_quantumLeft}) : $currentProcess->{_quantumLeft} ;
							$currentProcess->{_quantumLeft} = 0
					}	
							
										
					
					if($self->{_quantumUltUsed} eq $self->{_ThreadQuantum}){
							$currentProcess->{_quantumLeft} = 0;
							
							push @{$self->{_auxProc}->{_ults}}, $currentProcess;
							
							if($self->{_antName} eq $currentProcess->getName()){ #Cuando SO es FIFO y cuando el unico klt que queda es donde corre el ult.
								push @{$self->{_auxList}}, $self->{_auxProc};
								$self->{_auxProc}=undef;
							}
								
							
							$self->{_antName} = $currentProcess->getName();	
								
							$self->{_quantumUltUsed} = 0;
					}else{
							if(!($self->{_ProcessAlgorithm} eq "FIFO")){			
								if($self->{_quantumUsed} == $self->{_quantum}){
									$currentProcess->{_quantumLeft} = $self->{_ThreadQuantum} - $self->{_quantumUltUsed};
								}
							}
						
						else{
							$currentProcess->{_quantumLeft} = 0;
						}
																
							unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
					}
				
			 }else{
					$self->{_quantumUltUsed2} +=1;	
					$self->{_quantumUltUsed2} += $currentProcess->{_quantumLeft} ? ($self->{_ThreadQuantum} - $currentProcess->{_quantumLeft}) : $currentProcess->{_quantumLeft} ;		
				
					if($self->{_quantumUltUsed2} eq $self->{_ThreadQuantum}){
							$currentProcess->{_quantumLeft} = 0;
							
							push @{$self->{_auxProc}->{_ults}}, $currentProcess;
							
							if($self->{_ProcessAlgorithm} eq "FIFO" || $self->{_antName} eq $currentProcess->getName()){ #Cuando SO es FIFO y cuando el unico klt que queda es donde corre el ult.
								push @{$self->{_auxList}}, $self->{_auxProc};
								$self->{_auxProc}=undef;
							}
								
							
							$self->{_antName} = $currentProcess->getName();	
								
							$self->{_quantumUltUsed2} = 0;
					}else{
							if(!($self->{_ProcessAlgorithm} eq "FIFO")){			
								if($self->{_quantumUsed2} == $self->{_quantum}){
									$currentProcess->{_quantumLeft} = $self->{_ThreadQuantum} - $self->{_quantumUltUsed2};
								}
							}
						
						else{
							$currentProcess->{_quantumLeft} = 0;
						}
																
							unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
					}
				
			}
		}
		
		case "SPN"{
			unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
		}
		
		case "SRT" {
			
			unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
		}
		
		case "HRRN"{
			unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
		}	
	}
}

sub _ProcessUltByBlocking{
	my( $self, $currentProcess ) = @_;
	
	switch($self->{_ultAlgorithmin}){
		case "FIFO" {
			push @{$self->{_auxProc}->{_ults}}, $currentProcess; #SI es FIFO y está bloquedo se va a poner al final de la cola de ults.
		}
		case "RR" {
				push @{$self->{_auxProc}->{_ults}}, $currentProcess;
		}
		case "SPN" {
			
			push @{$self->{_auxProc}->{_ults}}, $currentProcess;
			$self->_OrderUltByMinor();
		}
		case "SRT" {
			
			push @{$self->{_auxProc}->{_ults}}, $currentProcess;
			$self->_OrderUltByMinor();
		}
		case "HRRN"{
			$currentProcess->{_startTime} = $self->{_ticks};
			push @{$self->{_auxProc}->{_ults}}, $currentProcess;
			$self->{_auxProc}->orderUltByPriorityHRRN($self->{_ticks})
		}
	}
}

sub _OrderUltByMinor{
	my( $self ) = @_;
	
	my $ultLenght;
	my $aux;
	
		if($self->{_auxProc}->hasUlts()){
			$ultLenght = @{$self->{_auxProc}->{_ults}};
			
			for(my $i=$ultLenght-1;$i>0;$i--){
					if($self->{_auxProc}->{_ults}[$i]->{_bursts}[0]->getValue() < $self->{_auxProc}->{_ults}[$i-1]->{_bursts}[0]->getValue()){
						$aux = $self->{_auxProc}->{_ults}[$i-1];
						$self->{_auxProc}->{_ults}[$i-1] = $self->{_auxProc}->{_ults}[$i];
						$self->{_auxProc}->{_ults}[$i] = $aux;
			}
		}
	}
	
	
}

sub _moveToReady{
	my($self) = @_;
	
	push $self->{_ready}, @{$self->{_auxList}};
	$self->{_auxList} = [];
}

sub _rearrangeIO{
	my($self) = @_;
	my $process;
	foreach $process(@{$self->{_auxIO}}){
			if(!$process->hasUlts()){
				my @bursts = @{$process->getBursts()};
					switch($bursts[0]->getName()){
						case "io1" { 
						push $self->{_IO1}, $process;
						}
						case "io2" { 
						push $self->{_IO2}, $process;
						}
						case "io3" { 
						push $self->{_IO3}, $process;
					}
				}
			
			}else{
				my @bursts = @{$process->{_ults}[0]->getBursts()};
				switch($bursts[0]->getName()){
				case "io1" { 
					push $self->{_IO1}, $process;
				}
				case "io2" { 
					push $self->{_IO2}, $process;
				}
				case "io3" { 
					push $self->{_IO3}, $process;
				}
				
			}
		
	  	}
	}
	$self->{_auxIO} = [];
}

sub _doTickIO1{
	my( $self ) = @_;
	
	my $hasUlts=0;
	my $currentProcess = $self->{_IO1}[0];
	
	if($currentProcess){ 
		
				if($currentProcess->hasUlts()){
					$hasUlts=1;
					$self->{_auxProc} = $currentProcess;
					$currentProcess = shift @{$self->{_auxProc}->{_ults}};
				}
		
		
				my @bursts = @{$currentProcess->getBursts()};
				my $io = $bursts[0]->getValue();
				$bursts[0]->setValue($io-1);
		
				if($bursts[0]->getValue()<=0){
					shift @bursts;
					shift @{$self->{_IO1}};
					$currentProcess->setBursts(\@bursts);
					@bursts = @{$currentProcess->getBursts()};
					my $burstsLength = @bursts;
						  if($burstsLength){
								if($hasUlts){
								  	if($bursts[0]->getName() eq "io1" || $bursts[0]->getName() eq "io2" || $bursts[0]->getName() eq "io3"){
										unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
										push $self->{_auxIO}, $self->{_auxProc};
									}else{
											$self->_ProcessUltByBlocking($currentProcess);
											$currentProcess = $self->{_auxProc};
													
											if($currentProcess->hasUlts()){
												push @{$self->{_auxList}}, $currentProcess;
											}
									}			
								}else{
									if($bursts[0]->getName() eq "io1" || $bursts[0]->getName() eq "io2" || $bursts[0]->getName() eq "io3"){
										push $self->{_auxIO}, $currentProcess;
									}else{
										push @{$self->{_auxList}}, $currentProcess;
									}
								}
								
						  }else{
						   		if($hasUlts){
						  			if($self->{_auxProc}->hasUlts){
						  				push @{$self->{_auxList}}, $self->{_auxProc}
						  			}
						  		}	
						  } 
				}else{
					if($hasUlts){
						unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
					}
				}
}
}

sub _doTickIO2{
	my( $self ) = @_;
	
	my $hasUlts=0;
	my $currentProcess = $self->{_IO2}[0];
	
	if($currentProcess){ 
		
				if($currentProcess->hasUlts()){
					$hasUlts=1;
					$self->{_auxProc} = $currentProcess;
					$currentProcess = shift @{$self->{_auxProc}->{_ults}};
				}
				
				my @bursts = @{$currentProcess->getBursts()};
				my $io = $bursts[0]->getValue();
				$bursts[0]->setValue($io-1);
		
				if($bursts[0]->getValue()<=0){
					shift @bursts;
					shift @{$self->{_IO2}};
					$currentProcess->setBursts(\@bursts);
					@bursts = @{$currentProcess->getBursts()};
					my $burstsLength = @bursts;
						  if($burstsLength){
								if($hasUlts){
								  	if($bursts[0]->getName() eq "io1" || $bursts[0]->getName() eq "io2" || $bursts[0]->getName() eq "io3"){
										unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
										push $self->{_auxIO}, $self->{_auxProc};
									}else{
											$self->_ProcessUltByBlocking($currentProcess);
											$currentProcess = $self->{_auxProc};
													
											if($currentProcess->hasUlts()){
												push @{$self->{_auxList}}, $currentProcess;
											}
									}			
							}else{
									if($bursts[0]->getName() eq "io1" || $bursts[0]->getName() eq "io2" || $bursts[0]->getName() eq "io3"){
										push $self->{_auxIO}, $currentProcess;
									}else{
										push @{$self->{_auxList}}, $currentProcess;
									}
							}
								
						  }else{
						   		if($hasUlts){
						  			if($self->{_auxProc}->hasUlts){
						  				push @{$self->{_auxList}}, $self->{_auxProc}
						  			}
						  		}	
						  } 
				}else{
					if($hasUlts){
						unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
					}
				}
	}
}

sub _doTickIO3{
	my( $self ) = @_;
	
	my $hasUlts=0;
	my $currentProcess = $self->{_IO3}[0];
	
	if($currentProcess){ 
		
				if($currentProcess->hasUlts()){
					$hasUlts=1;
					$self->{_auxProc} = $currentProcess;
					$currentProcess = shift @{$self->{_auxProc}->{_ults}};
				}
		
		
				my @bursts = @{$currentProcess->getBursts()};
				my $io = $bursts[0]->getValue();
				$bursts[0]->setValue($io-1);
		
				if($bursts[0]->getValue()<=0){
					shift @bursts;
					shift @{$self->{_IO3}};
					$currentProcess->setBursts(\@bursts);
					@bursts = @{$currentProcess->getBursts()};
					my $burstsLength = @bursts;
						  if($burstsLength){
								if($hasUlts){
								  	if($bursts[0]->getName() eq "io1" || $bursts[0]->getName() eq "io2" || $bursts[0]->getName() eq "io3"){
										unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
										push $self->{_auxIO}, $self->{_auxProc};
									}else{
											$self->_ProcessUltByBlocking($currentProcess);
											$currentProcess = $self->{_auxProc};
													
											if($currentProcess->hasUlts()){
												push @{$self->{_auxList}}, $currentProcess;
											}
									}			
								}else{
									if($bursts[0]->getName() eq "io1" || $bursts[0]->getName() eq "io2" || $bursts[0]->getName() eq "io3"){
										push $self->{_auxIO}, $currentProcess;
									}else{
										push @{$self->{_auxList}}, $currentProcess;
									}
								}
								
						  }else{
						   		if($hasUlts){
						  			if($self->{_auxProc}->hasUlts){
						  				push @{$self->{_auxList}}, $self->{_auxProc}
						  			}
						  		}	
						  } 
				}else{
					if($hasUlts){
						unshift @{$self->{_auxProc}->{_ults}}, $currentProcess;
					}
				}
	}
}

sub putProcess{
	my( $self, @process ) = @_;
	my $proc;
	
	switch($self->{_ultAlgorithmin}){
		case "SPN" {
			foreach $proc (@process){
				if($proc->hasUlts()){
					$proc->orderUltByMinor();
				}
					
			}
		}		
	}
		
	push $self->{_ready}, @process;
}

sub getExecuting{
	my( $self ) = @_;
	
	return $self->{_executing};
}

sub getExecuting2{
	my( $self ) = @_;
	
	return $self->{_executing2};
}

sub getIO1{
	my( $self ) = @_;
	my $ret = undef;
	my $currentProcess = $self->{_IO1}[0];
	if($currentProcess){
				
		if($currentProcess->hasUlts()){
			$currentProcess = $currentProcess->{_ults}[0];
		}
		
		$ret = $currentProcess;
	}
	
	return $ret;
}

sub getIO2{
	my( $self ) = @_;
	my $ret = undef;
	my $currentProcess = $self->{_IO2}[0];
	if($currentProcess){
		
		if($currentProcess->hasUlts()){
			$currentProcess = $currentProcess->{_ults}[0];
		}
		
		$ret = $currentProcess;
	}
	return $ret;
}

sub getIO3{
	my( $self ) = @_;
	my $ret = undef;
	my $currentProcess = $self->{_IO3}[0];
	if($currentProcess){
		
		if($currentProcess->hasUlts()){
			$currentProcess = $currentProcess->{_ults}[0];
		}
		
		$ret = $currentProcess;
	}
	return $ret;
}

sub getIO1List{
	my( $self ) = @_;
	return $self->{_IO1};
}

sub getIO2List{
	my( $self ) = @_;
	return $self->{_IO2};
}

sub getIO3List{
	my( $self ) = @_;
	return $self->{_IO3};
}

sub isProcessing(){
	my( $self ) = @_;
	my $ret = @{$self->{_IO1}}>0 || @{$self->{_IO2}}>0 || @{$self->{_IO3}}>0 || @{$self->{_ready}}>0 || defined($self->{_executing}) || defined($self->{_executing2});
	return $ret;
}

sub isRunningOS(){
	my( $self ) = @_;
	my $ret = (!defined($self->{_executing}) || ($self->{_kernels}>1 && !defined($self->{_executing2})));
	return $ret;
}
1;